/*******************************************************************************
 * Copyright 2007 Vidar Svansson
 *
 * This file is part of JAdapter.
 *
 * JAdapter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JAdapter is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JAdapter.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/

package org.jadapter;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.jadapter.exceptions.AdaptationException;

public class JAdapter<PROVIDES, FOR> 
        extends JComponent<PROVIDES, FOR> 
        implements Adapter<PROVIDES, FOR> {

    private Logger log = Logger.getLogger(JAdapter.class);


    Class<?> adapterClass;


    Adapter<PROVIDES, FOR> adapter;

    public JAdapter() {
        // empty constructor
    }

    public JAdapter(Class<?> adapter, Class<PROVIDES> iface, Class<FOR> context) {
        this.adapterClass = adapter;
        provides = iface;
        this.context = context;
    }

    public JAdapter(Adapter<PROVIDES, FOR> adapter) {
        this.adapter = adapter;
    }

    @SuppressWarnings("unchecked")
    public Object getAdapter(Object context, int checked) {
        if (checked == Adapter.UNCHECKED) {
            return getAdapter((FOR) context);
        }

        if (this.context.isAssignableFrom(context.getClass())) {
            return getAdapter((FOR) context);
        }

        throw new AdaptationException("Context is not suitable for adapter " + adapterClass.getName());
    }

    @SuppressWarnings("unchecked")
    public PROVIDES getAdapter(FOR context) {

        // do we have an adapter?
        if (adapter != null) {
            return adapter.getAdapter(context);
        }

        // Do we have the type already?
        if (provides.isAssignableFrom(this.context))
            return (PROVIDES) context;

        Constructor<?> constructor;
        PROVIDES adapter = null;
        
        // is this null adapter?
        if(context == null || context instanceof Nil) {
            try {
                constructor = adapterClass.getConstructor();
                adapter = (PROVIDES) constructor.newInstance();
                return adapter;

            } catch (Exception e) {
                // constructor(Type) not available
            }            
        }
                
        Method method = null;

        // Check if adapter has a constructor that accepts the adaptee's type
        try {
            constructor = adapterClass.getConstructor(this.context);
            adapter = (PROVIDES) constructor.newInstance(context);
            return adapter;

        } catch (Exception e) {
            // constructor(Type) not available
        }

        boolean isFactory = false;
        // try the empty constructor
        try {

            constructor = adapterClass.getConstructor();
            adapter = (PROVIDES) constructor.newInstance();

            if(adapter instanceof Adapter){
                Adapter<PROVIDES,FOR> internalAdapter = (Adapter)adapter;
                return internalAdapter.getAdapter(context);
            }

            // Look for a suitable setter method
            try {
                method = adapterClass.getDeclaredMethod("set"
                        + this.context.getSimpleName(), this.context);
            } catch (Exception e1) {
                // setter method not avail
                try {
                    method = adapterClass.getDeclaredMethod("get"
                            + provides.getSimpleName(), this.context);
                    isFactory = true;
                } catch (Exception e3) {
                    // factory method not avail
                }
            }

        } catch (Exception e) {
            // empty constructor not avail
        }

        if (method != null) {
            try {
                // set the context
                Object o = method.invoke(adapter, context);
                if (isFactory) {
                    if (o != null && provides.isAssignableFrom(o.getClass())) {
                        return (PROVIDES) o;
                    }
                    throw new AdaptationException("Could not invoke factory method " + method.getName());
                }
                return adapter;

            } catch (Exception e) {
                throw new AdaptationException("Could not invoke method " + method.getName(), e);
            }
        }

        // looks like we didn't find anything
        throw new AdaptationException();

    }

    public boolean provides(Class<?> iface, Object context) {
        Class klass = context instanceof Class ? (Class) context : context.getClass();        
        return iface.isAssignableFrom(this.provides) &&
               this.context.isAssignableFrom(klass);
    }

    public boolean canRegister() {
        return (!isRegistered() && provides != null && adapterClass != null
                && context != null);
    }

    // *** getters and setters *** //

    public Class<?> getAdapter() {
        return adapterClass;
    }

    public void setAdapter(Class<?> adapter) {
        this.adapterClass = adapter;
        register();
    }

}
